package com.ng.common.redis;

import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.connection.DataType;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
 
 
 
 

/**
 * Redis工具类
 *
 * @author lyf
 * 
 * @date 2017-07-17 21:12
 */
@Component
public class RedisUtils {
	
    //是否开启redis缓存  true开启   false关闭
    @Value("${spring.redis.open: false}")
    private boolean open = false;
	
    @Autowired(required=false)
    private RedisTemplate<String, Object>  redisTemplate;
//    @Autowired(required=false)
//    private ValueOperations<String, String> valueOperations;
    
    Map<String, Object> cache = new ConcurrentHashMap<String, Object>();
    /**
     * 缓存过期配置
     * key- 过期时间
     */
    Map<String, Long> cache_expire = new ConcurrentHashMap<String, Long>();
    
//    @Autowired
//    private HashOperations<String, String, Object> hashOperations;
//    @Autowired
//    private ListOperations<String, Object> listOperations;
//    @Autowired
//    private SetOperations<String, Object> setOperations;
//    @Autowired
//    private ZSetOperations<String, Object> zSetOperations;
    /**  默认过期时长，单位：秒 */
    public  static long DEFAULT_EXPIRE = 60*60  ;
    /**  不设置过期时长 */
    public final static long NOT_EXPIRE = -1;
    
    
	/**
	 * 默认30ms尝试一次
	 */
	private final static long LOCK_TRY_INTERVAL = 30L;
	/**
	 * 默认尝试20s
	 */
	private final static long LOCK_TRY_TIMEOUT = 1 * 1000L;
	/**
	 * 单个业务持有锁的时间30s，防止死锁
	 */
	private final static long LOCK_EXPIRE = 30 * 1000L;
    

    @SuppressWarnings({ "unchecked", "rawtypes" })
	public void set(String key, Object value, long expire){
    	if(key == null || value == null) {
			return ;
		}
    	if(!open) {
    		//System.out.println("key : " + key + ",value:" + value );
    		if(DEFAULT_EXPIRE <= 0) {
    			return;
    		}
    		cache.put(key, value);
    		if(expire < 0) {
    			expire = DEFAULT_EXPIRE;
    		}
    		cache_expire.put(key, System.currentTimeMillis() + expire * 1000);
    		return ;
    	}

    	if(value instanceof java.util.List) {

    		List data = (List) value ;
    		if(data.isEmpty()) return ;
    		//key如果存在则覆盖

    		//String listData = JSON.toJSONString(value);
    		redisTemplate.opsForValue().set(key, value);



//    		redisTemplate.delete(key);
//    		redisTemplate.opsForList().rightPushAll(key, data);
    		/*ListOperations<String, Object> opsList = redisTemplate.opsForList();
    		for(int i = 0 ; i < data.size() ; i++) {
    			opsList.set(key, i, data.get(i));
    		} */

    	} else if(value instanceof Set) {
    		Set data = (Set) value ;
    		if(data.isEmpty()) return ;
    		redisTemplate.opsForSet().add(key, data.toArray());
    	} else if(value instanceof Map) {

    		Map map = (Map) value ;

    		if(map.isEmpty()) return ;
    		redisTemplate.opsForHash().putAll(key, (Map) value);
    	} else {

    		redisTemplate.opsForValue().set(key, value);
    	}

    	redisTemplate.expire(key, expire, TimeUnit.SECONDS);


    	// valueOperations.set(key, v);
//         if(expire != NOT_EXPIRE){
//             redisTemplate.expire(key, expire, TimeUnit.SECONDS);
//         }
    }
    
    /**
     * 重新设置key的过期时间
     * @param key
     * @param expire
     */
    public void expire(String key , int expire) {
    	if(key == null) return ;
    	if(!open) {
    		cache_expire.put(key, System.currentTimeMillis() + expire * 1000);
    		
    	} else {
    		redisTemplate.expire(key, expire, TimeUnit.SECONDS);
    	}
    }

    public void set(String key, Object value){
    	if(!open) {
    		set(key, value, DEFAULT_EXPIRE);
    		return ;
    	}
        set(key, value, DEFAULT_EXPIRE);
    }
    
    public Map<String, Object> getCache() {
		return cache;
	}
 
    @SuppressWarnings("unchecked")
   	public <T> T get(String key, Class<T> clazz, long expire) {
       	if(!open) {
       		if(DEFAULT_EXPIRE <= 0) {
       			return null;
       		}
       		clearExpire();
       		cache.get(key);
       		return null;
       	}

       	DataType dtype = redisTemplate.type(key);

       	if(clazz.equals(List.class)) {
       		return (T) redisTemplate.opsForValue().get(key); 
       	} else if(clazz == Set.class || dtype == DataType.SET) {
       		return (T) redisTemplate.opsForSet().members(key);
       	} else if(clazz == Map.class || dtype == DataType.HASH) {
       		return (T) redisTemplate.opsForHash().entries(key);
       	} else {
       		return (T) redisTemplate.opsForValue().get(key);
       	}


       	//Object value = redisTemplate.opsForValue().get(key);
       	//  String value = valueOperations.get(key);
//             if(expire != NOT_EXPIRE){
//                 redisTemplate.expire(key, expire, TimeUnit.SECONDS);
//             }
           //return value == null ? null : stringToObject(value.toString(), clazz);

       //\	return null;
       }

    @SuppressWarnings("unchecked")
	public <T> T get(String key, Class<T> clazz) {
    	if(!open) {
    		if(DEFAULT_EXPIRE <= 0) {
    			return null;
    		}
    		clearExpire();
    		return (T) cache.get(key);
    		//return null;
    	}
        return get(key, clazz, NOT_EXPIRE);
    }
    
     
   	public Object get(String key) {
       	if(!open) {
       		if(DEFAULT_EXPIRE <= 0) {
       			return null;
       		}
       		clearExpire();
       		return  cache.get(key);
       		//return null;
       	}
       	
    	DataType dtype = redisTemplate.type(key);
   	 
    	if( dtype == DataType.LIST) {
    		return  redisTemplate.opsForList().range(key, 0, -1);
    	} else if( dtype == DataType.SET) {
    		return  redisTemplate.opsForSet().members(key);
    	} else if( dtype == DataType.HASH) {
    		return  redisTemplate.opsForHash().entries(key);
    	} else {
    		return  redisTemplate.opsForValue().get(key);
    	}
           
    }
    
    public boolean containKey(String key) {
    	if(!open) {
    		if(DEFAULT_EXPIRE <= 0) {
    			return false;
    		}
    		clearExpire();
    		return cache.containsKey(key);
    	}
    	
    	return redisTemplate.hasKey(key);
    	//return false;
    }

  /*  private String get(String key, long expire  ) {
    	if(!open) {
    		clearExpire();
    		return (String) cache.get(key);
    		//return null;
    	}
    	String value = valueOperations.get(key);
        if(expire != NOT_EXPIRE){
            redisTemplate.expire(key, expire, TimeUnit.SECONDS);
        } 
        return  value;
    }
*/
   /* private String get(String key) {
    	if(!open) {
    		if(DEFAULT_EXPIRE <= 0) {
    			return null;
    		}
    		clearExpire();
    		return (String) cache.get(key);
    	}
        return get(key , String.class, NOT_EXPIRE);
    }*/

    public void delete(String key) {
    	if(!open) {
    		if(DEFAULT_EXPIRE <= 0) {
    			return ;
    		}
    		cache.remove(key);
    		cache_expire.remove(key);
    		return ;
    	}
         redisTemplate.delete(key);
    }
    
    public Set<String> patternKeys(String pattern){ 
    	if(!open) {
    		clearExpire();
    		
    		Set<String> keys = new HashSet<String>();
    		for(String key : cache.keySet()) {
    			if(key.matches(pattern)) {
    				keys.add(key);
    			}
    		}
    		
    	 
    		return keys;
    	}
    	 return redisTemplate.keys(pattern);
    	
    	 
    }
    
    public void deleteCollections(Collection<String> keys) {
    	if(!open) {
    		if(DEFAULT_EXPIRE <= 0) {
    			return ;
    		}
    		clearExpire();
    		for(String key : keys) {
    			cache.remove(key);
    		}
    		
    		return ;
    	}
    	 redisTemplate.delete(keys);
    }
    
    public void deleteContains(String str) {
   	 
    	deleteContains(str , null);
    }
    
    public void deleteContains(String str , IFilter<String> keyFilter) {
    	if(!open) {
    		if(DEFAULT_EXPIRE <= 0) {
    			return ;
    		}
    		clearExpire();
    		String pattern = ".*" + str + ".*";
    		Set<String> keys = cache.keySet();
    		if(keys != null)
    		for(String key : keys) {
    			if(key.matches(pattern) && (null == null || keyFilter.yes(key))) {
    				 
    				cache.remove(key);
    			}
    		}
    		
    		return ;
    	}
    	String pattern = "*" + str + "*";
     
    	Set<String> keys = patternKeys(pattern);
    	if(keys != null && !keys.isEmpty()) {
    		
    		if(keyFilter != null)
    			keys = keys.stream().filter(t->keyFilter.yes(t)).collect(Collectors.toSet());
    		
    		deleteCollections(keys);
    	}
    	
    }
    

    public void deleteStartKey(String startKey) {
    	if(!open) {
    		if(DEFAULT_EXPIRE <= 0) {
    			return ;
    		}
    		String pattern = startKey + ".*";
    		clearExpire();
    		Set<String> keys = cache.keySet();
    		if(keys != null)
    		for(String key : keys) {
    			if(key.matches(pattern)) {
    				System.out.println("clear cache key: " + key);
    				cache.remove(key);
    			}
    		}
    		
    		return ;
    	}
    	String pattern = startKey + "*";
    	Set<String> keys = patternKeys(pattern);
    	if(keys != null && !keys.isEmpty()) {
    		deleteCollections(keys);
    	}
    	
    }
    
    
    
    public String arrayToString(Object[] objs) {
    	
    	if(objs == null || objs.length == 0) {
    		return "" ;
    	}
    	
    	StringBuffer sb = new StringBuffer();
    	
    	for(Object o : objs) {
    		 
    		sb.append(o == null ? "" : o.toString());
    		sb.append(",");
    	}
    	
    	return sb.toString();
    	
    }
    
    public String mapToString(Map<String,Object> map) {
    	
    	if(map == null || map.isEmpty()) {
    		return "" ;
    	}
    	
    	Set<String> keys_ = map.keySet();
    	
    	String[] keys = keys_.toArray(new String[0]);
    	
    	// sort
    	Arrays.sort(keys);
    	
    	StringBuffer sb = new StringBuffer();
    	
    	for(String key : keys) {
    		Object v = map.get(key);
    		String vstr = v != null ? v.toString() : "" ;
    		sb.append(key + "." + vstr + ";");
    	}
    	
    	if(sb.length() > 100) {
    		// 取hash 然后 前后和1/2 1/4 1/8位得字符串
    		
    		StringBuffer ret = new StringBuffer();
    		 
    		ret.append( sb.length() + "_" + sb.charAt(0) + "" + sb.charAt(sb.length() - 1));
    		 
    		// 长度
    		int len = sb.length();
    		for(int i = 2 ; i < 50 ; i++) {
    			char c = sb.charAt(len / i) ;
    			if(c == 13 || c == 10 || c== 32) continue ;
    			ret.append(c);
    		}
    		 
    		return ret.toString();
    	 }
    	
    	return sb.toString();
    	
    }

    public synchronized void clearExpire() {
     	
    	if(DEFAULT_EXPIRE <= 0) {
			return ;
		}
    	long ct = System.currentTimeMillis();
    	
    	String[] keys = cache_expire.keySet().toArray(new String[0]);
    	
    	for(String key : keys) {
    		Long time = cache_expire.get(key);
    		if(time != null && time < ct) {
    			cache.remove(key);
    			cache_expire.remove(key);
    		}
    	}
    	
    	 
    	
    } 
    
    
    


	/**
	 * 获取锁
	 *
	 * @param key 锁Key
	 * @return 是否获取锁
	 */
	public boolean lock(String key) {
		return getLock(key, 0, LOCK_EXPIRE, TimeUnit.MILLISECONDS);
	}

	/**
	 * 获取锁
	 *
	 * @param key        锁Key
	 * @param expire     有效期
	 * @param expireUnit 有效期时间单位
	 * @return 是否获取锁
	 */
	public boolean lock(String key, long expire, TimeUnit expireUnit) {
		return getLock(key, 0, expire, expireUnit);
	}

	/**
	 * 尝试获取锁
	 *
	 * @param key 锁Key
	 * @return 是否获取锁
	 */
	public boolean tryLock(String key) {
		return tryLock(key, LOCK_TRY_TIMEOUT, TimeUnit.MILLISECONDS);
	}

	/**
	 * 尝试获取锁
	 *
	 * @param key     锁Key
	 * @param timeout 等待超时时间
	 * @param unit    等待超时时间单位
	 * @return 是否获取锁
	 */
	public boolean tryLock(String key, long timeout, TimeUnit unit) {
		// 超时时间转成毫秒
		timeout = TimeUnit.MILLISECONDS.convert(timeout, unit);
		return getLock(key, timeout, LOCK_EXPIRE, TimeUnit.MILLISECONDS);
	}

	/**
	 * 尝试获取锁
	 *
	 * @param key         锁Key
	 * @param timeout     等待超时时间
	 * @param timeoutUnit 等待超时时间单位
	 * @param expire      有效期
	 * @param expireUnit  有效期时间单位
	 * @return
	 */
	public boolean tryLock(String key, long timeout, TimeUnit timeoutUnit, long expire, TimeUnit expireUnit) {
		// 超时时间转成毫秒
		timeout = TimeUnit.MILLISECONDS.convert(timeout, timeoutUnit);
		return getLock(key, timeout, expire, expireUnit);
	}

	/**
	 * 释放锁
	 *
	 * @param key 锁Key
	 */
	public void unlock(String key) {
		Long oldExpireTime = (Long) redisTemplate.opsForValue().get(key);
		if (null != oldExpireTime && oldExpireTime >= System.currentTimeMillis()) {
			// 大于过期时间，则删除key
			redisTemplate.delete(key);
		}
	}

	/**
	 * 获取锁
	 *
	 * @param key     锁键值
	 * @param timeout 超时时间
	 * @param time    全局锁生命周期
	 * @param unit    时间单位
	 * @return 是否获取到锁
	 */
	private boolean getLock(String key, long timeout, long time, TimeUnit unit) {
		try {
			long startTimeMillis = System.currentTimeMillis();
			do {
				long newValue = System.currentTimeMillis() + TimeUnit.MILLISECONDS.convert(time, unit);
				Boolean isOk = redisTemplate.opsForValue().setIfAbsent(key, newValue);
				if (isOk) {
					// 获得锁
					redisTemplate.expire(key, time, unit);
					return true;
				}
				// 获取过期时间
				Long oldExpireTime = (Long) redisTemplate.opsForValue().get(key);
				if (null == oldExpireTime) {
					oldExpireTime = 0L;
				}
				if (oldExpireTime >= System.currentTimeMillis()) {
					// 不小于系统时间并且过了超时时间，则不获取锁
					if ((System.currentTimeMillis() - startTimeMillis) > timeout) {
						return false;
					}
					System.out.println("thread sleep");
					// 休眠
					Thread.sleep(LOCK_TRY_INTERVAL);
				}
				// 新的过期时间
				long newExpireTime = System.currentTimeMillis() + TimeUnit.MILLISECONDS.convert(time, unit);
				Long currentExpireTime = (Long) redisTemplate.opsForValue().getAndSet(key, newExpireTime);
				if (null == currentExpireTime) {
					currentExpireTime = 0L;
				}
				if (currentExpireTime.equals(oldExpireTime)) {
					// 获取到锁
					redisTemplate.expire(key, time, unit);
					return true;
				}
			} while (true);
		} catch (Exception e) {
			return false;
		}
	}

}
